/*
	registry 表示注册中心
*/
package registry

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"sync"
	"time"
)

// reg 注册处全局变量
var reg = registry{
	registrations: make(map[string]Registration),
	mux:           new(sync.RWMutex),
}

// registry 表示注册处，记录注册的服务
type registry struct {
	registrations map[string]Registration
	mux           *sync.RWMutex
}

// add 注册服务
func (r *registry) add(reg Registration) error {
	//上锁
	r.mux.Lock()
	defer r.mux.Unlock()
	//更新数据
	r.registrations[reg.ServiceName] = reg
	//给service发送它依赖的服务的URL
	err := r.sendRequiredServices(reg)
	//把更新通知给其他service
	r.notify(Patch{
		Added: []PatchEntry{
			{
				Name: reg.ServiceName,
				URL:  reg.ServiceURL,
			},
		}})
	fmt.Println("[registry add] success")
	log.Println("[after add]", r.registrations)
	return err
}

// sendRequiredServices 给service发送它依赖的服务的URL
func (r *registry) sendRequiredServices(reg Registration) error {
	//准备数据
	var p Patch
	for _, reqService := range reg.RequiredServices {
		reg, ok := r.registrations[reqService]
		if ok {
			p.Added = append(p.Added, PatchEntry{
				Name: reg.ServiceName,
				URL:  reg.ServiceURL,
			})
		}
	}
	//以更新的形式告诉service
	err := r.sendPatch(p, reg.ServiceUpdateURL)
	if err != nil {
		return err
	}
	return nil
}

// notify 把更新通知给其他service
func (r *registry) notify(patch Patch) {
	//遍历已注册的服务
	for _, reg := range r.registrations {
		//每个service开一个goroutine
		go func(reg Registration) {

			//准备发送的数据
			p := Patch{
				Added:   []PatchEntry{},
				Removed: []PatchEntry{},
			}
			//标记是否要更新
			isUpdate := false

			//检查service所有依赖的服务
			for _, reqService := range reg.RequiredServices {
				//检查新注册的是否要更新
				for _, added := range patch.Added {
					if added.Name == reqService {
						p.Added = append(p.Added, added)
						isUpdate = true
					}
				}
				//检查取消的是否要更新
				for _, removed := range patch.Removed {
					if removed.Name == reqService {
						p.Removed = append(p.Removed, removed)
						isUpdate = true
					}
				}
			}

			//判断是否发送更新
			if isUpdate {
				err := r.sendPatch(p, reg.ServiceUpdateURL)
				if err != nil {
					log.Println(err)
					return
				}
			}

		}(reg)
	}
}

// sendPatch 发送更新到service
func (r *registry) sendPatch(p Patch, url string) error {
	//准备数据
	d, err := json.Marshal(p)
	if err != nil {
		return err
	}
	//发送更新
	_, err = http.Post(url, "application/json", bytes.NewBuffer(d))
	if err != nil {
		return err
	}
	return nil
}

// remove 取消服务
func (r *registry) remove(url string) error {
	//上锁
	r.mux.Lock()
	defer r.mux.Unlock()
	log.Println("[remove]", r.registrations)
	for _, reg := range r.registrations {
		if reg.ServiceURL == url {
			//通知其他service这个服务取消了
			r.notify(Patch{
				Removed: []PatchEntry{
					{
						Name: reg.ServiceName,
						URL:  reg.ServiceURL,
					},
				},
			})
			//更新数据
			delete(r.registrations, reg.ServiceName)
			//找到服务后，不要再找了
			return nil
		}
	}
	//返回错误
	return fmt.Errorf("[remove] service at %s not found", url)
}

// heartbeat 定时发送心跳检测
func (r *registry) heartbeat(freq time.Duration) {
	for {
		var wg sync.WaitGroup
		//对每个service
		for _, reg := range r.registrations {
			wg.Add(1)
			//由于会尝试多次，所以后台运行
			go func(reg Registration) {
				defer wg.Done()
				success := true
				//尝试3次
				for attemps := 0; attemps < 3; attemps++ {
					res, err := http.Get(reg.HeartbeatURL)
					if err != nil {
						log.Println(err)
					} else if res.StatusCode == http.StatusOK {
						//如果成功
						log.Printf("Heartbeat check passed for %v", reg.ServiceName)
						//如果之前尝试失败，它会取消该服务，这时成功了应把服务注册回去
						if !success {
							r.add(reg)
						}
						break
					}
					//如果失败
					log.Printf("Heartbeat check failed for %v", reg.ServiceName)
					//如果是第一次失败，则取消该服务
					if success {
						success = false
						r.remove(reg.ServiceURL)
					}
					time.Sleep(time.Second) //间隔1秒
				}
			}(reg)
		}
		//等待所有心跳检测完成
		wg.Wait()
		time.Sleep(freq)
	}
}

var once sync.Once

func SetupRegistryService() {
	//只初始化一次
	once.Do(func() {
		//后台每隔3秒给所有service发送一次心跳检测
		go reg.heartbeat(3 * time.Second)
	})
}

const ServerPost = ":3000"
const ServicesURL = "http://localhost" + ServerPost + "/services"

// RegistryService 提供HTTP服务
type RegistryService struct{}

func (s *RegistryService) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	log.Println("Request received")
	switch r.Method {
	// 注册service
	case http.MethodPost:
		//解析json
		dec := json.NewDecoder(r.Body)
		var r Registration
		err := dec.Decode(&r)
		if err != nil {
			log.Println(err)
			w.WriteHeader(http.StatusBadRequest)
			return
		}
		//解析json成功，在注册处添加该服务
		log.Printf("Adding service: %v with URL: %s\n", r.ServiceName, r.ServiceURL)
		err = reg.add(r)
		if err != nil {
			log.Println(err)
			w.WriteHeader(http.StatusBadRequest)
			return
		}
	// 取消service
	case http.MethodDelete:
		//Body中是要取消的服务URL
		payload, err := ioutil.ReadAll(r.Body)
		if err != nil {
			log.Println(err)
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
		//转成url
		url := string(payload)
		//从注册处移除该服务
		log.Printf("Removing service at URL: %s", url)
		err = reg.remove(url)
		if err != nil {
			log.Println(err)
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
	// 不允许的请求
	default:
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}
}
